package juc.thread;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ShareData{
    private  int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws Exception{
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"\t=========================");
        try {
            // 判断 flag==true 挂起
            while (number!=0){
                System.out.println(Thread.currentThread().getName()+" 生成者等待， flag"+number);
                condition.await();
            }
            // flag==false
            number++;
            System.out.println(Thread.currentThread().getName()+"\t 生产者生产number\t"+number);
            // 这里如果使用signal()方法产生了死锁 ,可以采用singalAll()
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"\t 生产者释放锁 执行完毕");
            lock.unlock();
        }
    }

    public void decrement() throws  Exception{
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"\t++++++++++++++++++++++++");
       //TimeUnit.SECONDS.sleep(1);
        try {
            // 判断
            while (number==0){
                System.out.println(Thread.currentThread().getName()+"\t 消费者等待");
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"\t 消费者生产number\t"+number);
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"\t 消费者释放锁 执行完毕");
            lock.unlock();
        }
    }
}

/**
 * @auther dyhan
 * @create 2020/3/12
 *
 * 题目
 * notify()/singal() 唤醒等待时间最长的一个线程
 *
 * Object类型的notify() 方法以及 condition.singal()方法都可能会出现死锁现象
 *
 * 假设现在有一个 资源类ShareData  其中有一个变量 number,默认值为0（表示暂时没有生成任何东西，消费者不能消费）， 两个方法 increment() decrement()
 * 其中，increment()模拟生产者，如果没有数据（number=0） 就生产（number++）,如果已经有数据了（number!=0）暂时停止生产(调用await()挂起线程)，等待消费之后再次生成
 *      decrement()模拟消费者，如果有数据（number!=0） 就消费（number--）,如果没有数据（number==0）就停止消费（调用await()挂起线程），等待生产者生产之后再消费
 *
 * 现在有10 个线程， 5个模拟生产者生产数据（调用increment()方法），5个模拟消费者消费数据（调用decrement() 方法）
 * 生成者：P1,P2,P3,P4,P5
 * 消费者：C1,C2,C3,C4,C5
 *
 * 假设第一次P1抢占到锁，并生产了一条数据 number+1,在P1 释放锁之后，P2,P3,P4,P5依次抢占到锁，但是现在由于已经生成了数据（number!=0）,所以P2,P3,P4,P5依次进入阻塞队列等待呗唤醒
 * 此时C1抢占到锁，消费数据 numbner-1,并唤醒了一个线程，由于singal()会唤醒等待时间最长的一个线程，所以这里将唤醒P2线程
 * 这是P2,C2,C3,C4,C5 同时抢占锁，
 * 假设P2没有抢占到线程，并且C2,C3,C4,C5依次抢占到锁，但是由于现在没有可以被消费的数据，所以C2,C3,C4,C5，依次被挂起，
 * 这时由于P2在之前已经被唤醒了，所以这个时候P2抢占到了锁， 那么P2 将会生成一条数据 number++,并唤醒了一个线程
 * 现在各个线程的状态是P1、C1执行完成,P2正在执行，其他挂起，那么由于P3被挂起的时间最长，所以这时P3被唤醒，但是由于P2线程已经生产了一个数据
 * 所以这个时候number!=0，那么P3 线程将直接被挂起，并且不会通知其他线程结束挂起状态，所以现在所有的线程都被挂起了，程序无法正常运行结束
 *
 *
 * 这个时候如果将singal()方法换成singalAll() 方法就可以解决这个问题
 * 因为调用singalAll()方法将唤醒所有的线程，这个时候就算P2 被挂起没有通知其他线程，
 * 那么其他线程由于已经被singalAll()方法唤醒，所以任然会继续抢占锁，并执行代码
 * 这时消费者可能抢占到锁，并消费，消费完成后生产者继续生产，就不会产生死锁的情况
 *
 *
 *
 * notify和notifyAll的区别：
 * notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
 *
 * void notify(): 唤醒一个正在等待该对象的线程。
 * void notifyAll(): 唤醒所有正在等待该对象的线程。
 * 两者的最大区别在于：
 *      notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态，变成等待该对象上的锁，一旦该对象被解锁，他们就会去竞争。
 *      notify他只是选择一个wait状态线程进行通知，并使它获得该对象上的锁，但不惊动其他同样在等待被该对象notify的线程们，
 *      当第一个线程运行完毕以后释放对象上的锁，此时如果该对象没有再次使用notify语句，即便该对象已经空闲，
 *      其他wait状态等待的线程由于没有得到该对象的通知，继续处在wait状态，直到这个对象发出一个notify或notifyAll，
 *      它们等待的是被notify或notifyAll，而不是锁。
 *
 */
public class ProdConsumerTraditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    shareData.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"prod"+i).start();
        }

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    shareData.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"consumer"+i).start();
        }

    }
}
